/*
 * Copyright (C) 2014 luke.
 * 
 * This file is part of Silvie.
 * 
 * Silvie is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Silvie is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Silvie.  If not, see <http://www.gnu.org/licenses/>.
 */

package silvie.gui.previewers;

import java.awt.Canvas;
import java.awt.Dimension;
import java.awt.Image;
import java.io.File;
import java.io.IOException;
import java.util.ArrayDeque;
import java.util.Queue;

import javax.swing.Timer;

import silvie.gui.Preview;
import utils.DoubleBufferedCanvas;
import utils.smacker.NoMoreFramesException;
import utils.smacker.SmackerDecoder;
import utils.smacker.SmackerHeader;

/**
 * AbstractPreviewer specialization for SMK videos using the Smacker library.
 * 
 * @author luke
 */
public class VideoPreviewer extends AbstractPreviewer {
	// Number of frames to cache
	private static final int FRAME_QUEUE_CAPACITY = 3;
	private static final int INITIAL_DISPLAY_DELAY = 100;
	private static final int DECODING_THREAD_SLEEP = 5;
	private static final String VIDEO_PREVIEWER_THREAD_NAME = "VideoPreviewer Thread";
	private SmackerDecoder decoder;
	private Dimension dimension;
	private int frameDuration;
	private boolean stopDecodingThread;
	private Image currentFrame;
	private Queue<Image> frameQueue;
	private Timer frameDisplayTimer;

	/**
	 * VideoPreviewer constructor.
	 * 
	 * @param preview the Preview to display on.
	 * @param decoder a SmackerDecoder for the SMK file.
	 * @param header the SmackerFile header for the SMK file.
	 */
	public VideoPreviewer(Preview preview, SmackerDecoder decoder, SmackerHeader header) {
		super(preview);
		this.decoder = decoder;
		this.dimension = new Dimension(header.width, header.height);
		this.frameDuration = (int) Math.round(((1 / header.fps) * 1000));
		this.stopDecodingThread = false;
		this.currentFrame = null;
		this.frameQueue = new ArrayDeque<Image>(FRAME_QUEUE_CAPACITY);
		this.frameDisplayTimer = null;
	}

	@Override
	public void doPreview() {
		Canvas canvas = new DoubleBufferedCanvas() {
			private static final long serialVersionUID = 1L;

			@Override
			public Image getOffscreenImage() {
				return VideoPreviewer.this.currentFrame;
			}

			@Override
			public Dimension getPreferredSize() {
				return VideoPreviewer.this.dimension;
			}
		};
		this.frameDisplayTimer = new Timer(this.frameDuration, e -> {
			synchronized (this.frameQueue) {
				this.currentFrame = this.frameQueue.poll();
				this.frameQueue.notify();
			}
			canvas.repaint();
		});
		Thread decodingThread = new Thread(() -> {
			try {
				VideoPreviewer.this.decoder.reset();
				while (!VideoPreviewer.this.stopDecodingThread) {
					try {
						// TODO Some videos don't work eg. monleft.smk
						VideoPreviewer.this.decoder.readNextFrame();
					} catch (NoMoreFramesException e) {
						VideoPreviewer.this.decoder.reset();
						// Loop the video
						continue;
					}
					Image frame = VideoPreviewer.this.decoder.getVideoDataBitmap();
					synchronized (VideoPreviewer.this.frameQueue) {
						while (this.frameQueue.size() > FRAME_QUEUE_CAPACITY && !VideoPreviewer.this.stopDecodingThread) {
							try {
								VideoPreviewer.this.frameQueue.wait();
							} catch (InterruptedException e) {
								e.printStackTrace();
							}
						}
						VideoPreviewer.this.frameQueue.offer(frame);
					}
					try {
						Thread.sleep(DECODING_THREAD_SLEEP);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			} catch (Exception e) {
				if (this.frameDisplayTimer.isRunning()) {
					this.frameDisplayTimer.stop();
				}
				e.printStackTrace();
				super.preview.setCanvas(e);
			}
		}, VIDEO_PREVIEWER_THREAD_NAME);
		decodingThread.start();
		this.frameDisplayTimer.setInitialDelay(INITIAL_DISPLAY_DELAY);
		this.frameDisplayTimer.start();
		super.preview.setCanvas(canvas, true);
	}

	@Override
	public void close() {
		if (this.frameDisplayTimer.isRunning()) {
			this.frameDisplayTimer.stop();
		}
		this.stopDecodingThread = true;
		synchronized (this.frameQueue) {
			/*
			 * In case the decoding thread was waiting, this will wake it up so
			 * it can actually stop
			 */
			this.frameQueue.notify();
		}
	}

	@Override
	public boolean save() {
		return false;
	}

	@Override
	public File doSave() throws IOException {
		return null;
	}
}
